﻿
using System;

using System.Net;
using System.Net.Sockets;

using System.Collections.Generic;

using System.Text;

namespace TcpAsyncClient
{
    sealed class ReceivedEvtArgs : EventArgs
    {
        private byte[] m_buffer;
        private int m_length;

        public ReceivedEvtArgs(byte[] buffer,int length)
        {
            m_buffer = buffer;
            m_length = length;
        }

        public byte[] RecvBuffer { get { return m_buffer; } }
        public int RecvLengthh { get { return m_length; } }
    }

    sealed class TcpClient : IDisposable
    {
        //****************************************//
        #region [ definition ]

        // member declared as constant is already static
        public const string EXIT_MSG = "exit";

        #endregion

        //****************************************//
        #region [ member variables ]

        public event EventHandler<ReceivedEvtArgs> ReceivedEvtHandler;
        public event EventHandler ClosedEvtHandler;

        private Socket m_socket;
        private bool m_disposed = false;
        private byte[] m_buffer = new byte[1024];
        private IEnumerable<string> m_strEnumerable;

        #endregion

        //****************************************//
        #region [ property ]

        public IEnumerable<string> Content
        {
            set { m_strEnumerable = value; }
        }

        #endregion

        //****************************************//
        #region [ constructor and destructor ]

        public TcpClient()
        {
            m_socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
        }

        ~TcpClient()
        {
            Dispose(false);
        }

        #endregion

        //****************************************//
        #region [ public methods ]

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Run(string address, int port)
        {
            IPEndPoint remoteEndpoint = CreateEndPoint(address,port);
            m_socket.Connect(remoteEndpoint);

            // after connetion is built
            // launch an asynchronous receiving thread to read the echo-back content
            m_socket.BeginReceive(m_buffer, 0, m_buffer.Length, SocketFlags.None, OnReceivedCallback, null);

            foreach (string msg in m_strEnumerable)
            {
                if (msg.Equals(EXIT_MSG, StringComparison.OrdinalIgnoreCase))
                    break;

                byte[] sendbuffer = Encoding.ASCII.GetBytes(msg);
                m_socket.Send(sendbuffer, 0, sendbuffer.Length, SocketFlags.None);
            }
        }

        #endregion

        //****************************************//
        #region [ private methods ]

        private void OnReceivedCallback(IAsyncResult asynResult)
        {
            int recvLength = 0;
            try
            {
                recvLength = m_socket.EndReceive(asynResult);
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.ConnectionReset)
                    recvLength = 0;// indicating connection is closed by remote terminal
                else
                {
                    Dispose();
                    throw ex;
                }
            }

            if (recvLength > 0)
            {
                FireReceivedEvent(recvLength);
                m_socket.BeginReceive(m_buffer, 0, m_buffer.Length, SocketFlags.None, OnReceivedCallback, null);
            }
            else
            {
                FireClosedEvent();
                Dispose();
            }
        }

        private void Dispose(bool isdisposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (isdisposing)
                    m_socket.Close();
            }
            finally
            {
                m_disposed = true;
            }
        }

        private IPEndPoint CreateEndPoint(string hostNameOrAddress, int port)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                return new IPEndPoint(IPAddress.Any, port);
            }
            else
            {
                IPAddress address;
                if (IPAddress.TryParse(hostNameOrAddress, out address))
                    // Use the provided IP address.
                    return new IPEndPoint(address, port);
                else
                    // Exception will occur if DNS lookup fails.
                    return new IPEndPoint(Dns.GetHostEntry(hostNameOrAddress).AddressList[0], port);
            }
        }

        private void FireReceivedEvent(int length)
        {
            if (ReceivedEvtHandler != null)
                ReceivedEvtHandler(this,new ReceivedEvtArgs(m_buffer,length));
        }

        private void FireClosedEvent()
        {
            if (ClosedEvtHandler != null)
                ClosedEvtHandler(this,EventArgs.Empty);
        }

        #endregion
    }
}